package com.hmdp.utils;

import cn.hutool.core.date.DateUtil;
import cn.hutool.core.util.BooleanUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.json.JSONUtil;
import lombok.extern.slf4j.Slf4j;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Component;

import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;
import java.util.function.Function;

@Component
@Slf4j
public class RedisCacheUtil {
    private final StringRedisTemplate stringRedisTemplate;
    private ExecutorService cacheRebuildService= Executors.newFixedThreadPool(1);
    public RedisCacheUtil(StringRedisTemplate stringRedisTemplate) {//利用构造函数让spring注入 stringRedisTemplate
        this.stringRedisTemplate=stringRedisTemplate;
    }
    public void set(String key, Object bean, long time, TimeUnit unit){
           stringRedisTemplate.opsForValue().set(key, JSONUtil.toJsonStr(bean),time,unit);
    }
    public void setWithLogical(String key,Object bean,long logExpTime,TimeUnit unit){
        long l = DateUtil.current() + unit.toMillis(logExpTime);
        log.debug("下次到期时间:{}",l);
        stringRedisTemplate.opsForValue().set("lgt:"+ key,l+"");
           stringRedisTemplate.opsForValue().set(key,JSONUtil.toJsonStr(bean));
    }
    public <R,ID>R queryWithPassThrough(String keyPrefix, ID id, Class<R>beanClass, Function<ID,R>dbCallBack, long time, TimeUnit unit){
        //尝试从缓存中获取数据
        String key=keyPrefix+id;
        String json = stringRedisTemplate.opsForValue().get(key);
        if (StrUtil.isNotBlank(json)) { //命中
            return JSONUtil.toBean(json, beanClass);
        }
        if (json != null) {//如果该id对应数据为“”
            return null;
        }
        R bean = null;
        bean = dbCallBack.apply(id);
            if (bean == null) {
                //防止缓存穿透
                stringRedisTemplate.opsForValue().set(key,"");
                return null;
            }
            //写入缓存
            set(key,bean,time,unit);
        //返回数据
        return bean;
    }
    public <R,ID>R queryWithLogical(String keyPrefix, ID id, Class<R>beanClass, Function<ID,R>dbCallBack, long expireTime, TimeUnit unit){
        //尝试从缓存中获取数据
        String key=keyPrefix+id;
        String json = stringRedisTemplate.opsForValue().get(key);
        if (StrUtil.isBlank(json)) { //未命中
            return null;
        }
        //命中
        R bean = JSONUtil.toBean(json, beanClass);
        //查看是否逻辑过期
        String expTime = stringRedisTemplate.opsForValue().get("lgt:"+ key);
        log.debug("过期时间:{}",expTime);
        //未过期 返回结果
        log.debug("现在时间:{}",DateUtil.current());
        if(DateUtil.current()-Long.parseLong(expTime)<=0){
            log.debug("无需重建");
            return bean;
        }
        //过期 缓存重建
        //获取锁
        //成功 开启新线程进行缓存重建
        if(lock(key)){
            try {
                //doubleCheck
                //在并发过程中有了逻辑没过期的缓存
                expTime = stringRedisTemplate.opsForValue().get("lgt:"+ key);
                //未过期 返回结果
                if(DateUtil.current()-Long.parseLong(expTime)<=0){
                    return bean;
                }
                //doubleCheck
                //缓存重建
                cacheRebuildService.execute(() -> {
                    setWithLogical(key, dbCallBack.apply(id),expireTime,unit);
                });
            } catch (Exception e) {
                throw new RuntimeException(e);
            }finally {
                //释放锁
                unlock(key);
            }
        }
        //返回旧数据
        return bean;
    }
    private boolean lock(String key){
        //设置超时时间避免在极端条件下出现死锁
        return BooleanUtil.isTrue(stringRedisTemplate.opsForValue().setIfAbsent("lock:"+key,"",10,TimeUnit.SECONDS));
    }
    private void unlock(String key){
        stringRedisTemplate.delete("lock:"+key);
    }
}
